#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cv2
import numpy as np
import re
from typing import Dict, Optional, Tuple, List
from dataclasses import dataclass
from pathlib import Path
import datetime
import os
from PIL import Image
import time
import json

try:
    from rapidocr_onnxruntime import RapidOCR
    USE_RAPID_OCR = True
except ImportError:
    from paddleocr import PaddleOCR
    USE_RAPID_OCR = False

@dataclass
class BankCardInfo:
    """银行卡信息数据结构"""
    card_number: Optional[str] = None
    bank_name: Optional[str] = None
    card_type: Optional[str] = None
    holder_name: Optional[str] = None
    valid_date: Optional[str] = None
    
    def to_dict(self) -> Dict:
        """转换为字典格式"""
        return {
            'card_number': self.card_number,
            'bank_name': self.bank_name,
            'card_type': self.card_type,
            'holder_name': self.holder_name,
            'valid_date': self.valid_date
        }

class BankCardReader:
    """银行卡OCR识别器"""
    
    def __init__(self, use_fast_mode=True, max_image_size=1920):
        """初始化银行卡读取器"""
        self.use_fast_mode = use_fast_mode
        self.max_image_size = max_image_size
        
        # 初始化OCR引擎
        if USE_RAPID_OCR:
            self.ocr = RapidOCR()
        else:
            self.ocr = PaddleOCR(
                use_angle_cls=True,
                use_gpu=False,
                lang='ch',
                show_log=False,
                det_db_thresh=0.3,
                det_db_box_thresh=0.6,
                det_db_unclip_ratio=1.5,
                enable_mkldnn=True,
            )
        
        # 银行卡号正则表达式（16位或19位数字）
        self.card_patterns = {
            # 银行卡号：16位或19位数字，支持空格分隔，必须以3/4/5/6开头
            'card_number': [
                # 主要模式：16位数字（可能有空格分隔），以3/4/5/6开头
                r'([3-6]\d{3}\s*\d{4}\s*\d{4}\s*\d{4})',
                # 19位数字（可能有空格分隔），以3/4/5/6开头
                r'([3-6]\d{3}\s*\d{4}\s*\d{4}\s*\d{4}\s*\d{3})',
                # 紧凑模式：连续的16或19位数字，以3/4/5/6开头
                r'([3-6]\d{15,18})',
                # 银联卡专用模式：62开头
                r'(62\d{14,17})',
                # VISA卡专用模式：4开头
                r'(4\d{15,18})',
                # MasterCard专用模式：5开头
                r'(5\d{15,18})',
            ],
            # 有效期：MM/YY或MM/YYYY格式
            'valid_date': [
                r'(\d{2}/\d{2})',
                r'(\d{2}/\d{4})',
                r'有效期\s*[:：]?\s*(\d{2}/\d{2})',
                r'VALID\s*THRU\s*[:：]?\s*(\d{2}/\d{2})',
                r'MONTH/YEAR\s*.*?(\d{2}/\d{2})',
                r'THRU\s*(\d{2}/\d{2})',
                r'(\d{1,2}\s*/\s*\d{2,4})',  # 更灵活的格式
                r'(\d{4}/\d{2})',  # 可能识别成MMYY/DD格式
                r'(\d{2}[01]\d/\d{2})',  # 处理识别错误如1110/31
                r'(\d[!I1l]\d{2})',  # 处理OCR错误如1!10
                r'VALID\s+(\d{4}/\d{2})',  # VALID 1110/31格式
                r'VALID\s+(\d[!I1l]\d{2})',  # VALID 1!10格式
            ],
            # 持卡人姓名（通常在卡片底部）
            'holder_name': [
                r'持卡人\s*[:：]?\s*([A-Z\s]+)',
                r'CARDHOLDER\s*[:：]?\s*([A-Z\s]+)',
                r'^([A-Z]{2,}\s+[A-Z]{2,})$',  # 英文姓名格式
            ],
        }
        
        # 加载银行配置数据
        self.bank_names = self._load_bank_config()
    
    def resize_image_if_needed(self, image_path: str) -> str:
        """压缩图片以优化处理速度"""
        try:
            with Image.open(image_path) as img:
                width, height = img.size
                max_dimension = max(width, height)
                target_size = 1280
                
                if max_dimension > target_size:
                    scale_ratio = target_size / max_dimension
                    new_width = int(width * scale_ratio)
                    new_height = int(height * scale_ratio)
                    
                    resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                    temp_path = f"temp_resized_{int(time.time() * 1000000)}.jpg"
                    resized_img.save(temp_path, 'JPEG', quality=95)
                    return temp_path
                    
                return image_path
        except Exception as e:
            return image_path
    
    def ocr_text(self, image_path: str) -> Tuple[str, List]:
        """执行OCR识别，返回文本和边界框"""
        try:
            if USE_RAPID_OCR:
                result = self.ocr(image_path)
                
                if result is None or len(result) == 0:
                    return "", []
                
                # RapidOCR返回格式处理
                if isinstance(result, tuple) and len(result) >= 1:
                    ocr_result = result[0] if result[0] is not None else []
                else:
                    ocr_result = result if result is not None else []
                
                texts = []
                boxes = []
                
                for line in ocr_result:
                    if isinstance(line, list) and len(line) >= 2:
                        text = str(line[1]).strip()
                        if text:
                            texts.append(text)
                            if len(line) >= 1:
                                boxes.append(line[0])
                
                return ' '.join(texts), boxes
            
            else:
                result = self.ocr.ocr(image_path, cls=True)
                
                if not result or not result[0]:
                    return "", []
                
                texts = []
                boxes = []
                
                for line in result[0]:
                    if isinstance(line, list) and len(line) >= 2:
                        text = str(line[1][0]).strip()
                        if text:
                            texts.append(text)
                            boxes.append(line[0])
                
                return ' '.join(texts), boxes
                
        except Exception as e:
            return "", []
    
    def _clean_card_number(self, card_number: str) -> str:
        """清理银行卡号，去除空格和短横线"""
        if not card_number:
            return ""

        # 移除空格、短横线和其他特殊字符，保留数字
        cleaned = re.sub(r'[^\d]', '', card_number)

        # 验证长度（16-19位，符合银行卡号标准）
        if not (16 <= len(cleaned) <= 19):
            return ""

        # 验证开头数字：银行卡号不能以0开头
        if cleaned.startswith('0'):
            return ""

        # 验证有效的开头数字（中国大陆银行卡规范）
        # 4开头：VISA，5开头：MasterCard，6开头：银联等，3开头：American Express等
        valid_prefixes = ['3', '4', '5', '6']
        if not any(cleaned.startswith(prefix) for prefix in valid_prefixes):
            return ""

        return cleaned
    
    def _luhn_check(self, card_number: str) -> bool:
        """Luhn算法校验银行卡号"""
        if not card_number or not card_number.isdigit():
            return False
        
        # Luhn算法
        total = 0
        reverse_digits = card_number[::-1]
        
        for i, digit in enumerate(reverse_digits):
            n = int(digit)
            if i % 2 == 1:  # 从右边开始的奇数位置（即从左边开始的偶数位置）
                n *= 2
                if n > 9:
                    n = n // 10 + n % 10
            total += n
        
        return total % 10 == 0
    
    def _get_card_priority(self, card_number: str) -> int:
        """获取银行卡号优先级"""
        if not card_number:
            return 0
        
        # 优先级1：以62开头，长度为19位
        if card_number.startswith('62') and len(card_number) == 19:
            return 3
        
        # 优先级2：以62开头，长度为16-19位
        if card_number.startswith('62') and 16 <= len(card_number) <= 19:
            return 2
        
        # 优先级3：任意16-19位数字
        if 16 <= len(card_number) <= 19:
            return 1
        
        return 0
    
    def _extract_numbers_from_text(self, text: str) -> List[str]:
        """从文本中提取所有可能的数字序列"""
        # 提取所有连续的数字序列
        number_sequences = re.findall(r'\d+', text)
        return number_sequences
    
    def _find_card_number_candidates(self, text: str) -> List[str]:
        """寻找可能的银行卡号候选"""
        candidates = []
        
        # 1. 直接匹配完整的卡号
        for pattern in self.card_patterns['card_number']:
            matches = re.findall(pattern, text, re.IGNORECASE)
            for match in matches:
                cleaned = self._clean_card_number(match)
                if cleaned:
                    candidates.append(cleaned)
        
        # 2. 寻找分散的数字组合
        number_sequences = self._extract_numbers_from_text(text)
        
        # 按数字序列长度排序，优先组合长序列
        number_sequences.sort(key=len, reverse=True)
        
        # 尝试组合数字序列形成16位或19位卡号
        if len(number_sequences) >= 1:
            # 组合所有数字
            combined = ''.join(number_sequences)
            cleaned = self._clean_card_number(combined)
            if cleaned:
                candidates.append(cleaned)
            
            # 特殊处理：针对银联卡 62 开头的情况
            # 寻找以 62 开头的序列组合
            for i, seq in enumerate(number_sequences):
                if seq.startswith('62') and len(seq) >= 4:
                    # 以这个序列为起点，尝试组合后续数字
                    current_combined = seq
                    for j in range(i + 1, len(number_sequences)):
                        current_combined += number_sequences[j]
                        cleaned = self._clean_card_number(current_combined)
                        if cleaned and len(cleaned) in [16, 19]:
                            candidates.append(cleaned)
                            # 如果已经是19位，优先采用
                            if len(cleaned) == 19:
                                break
            
            # 智能组合：寻找最有可能的银行卡号组合
            # 优先组合较长的数字序列
            for i in range(len(number_sequences)):
                current_seq = number_sequences[i]
                
                # 如果当前序列长度 >= 13，可能是主要部分
                if len(current_seq) >= 13:
                    # 尝试与其他序列组合
                    for j in range(len(number_sequences)):
                        if i != j:
                            # 前置组合
                            combined1 = number_sequences[j] + current_seq
                            cleaned1 = self._clean_card_number(combined1)
                            if cleaned1:
                                candidates.append(cleaned1)
                            
                            # 后置组合
                            combined2 = current_seq + number_sequences[j]
                            cleaned2 = self._clean_card_number(combined2)
                            if cleaned2:
                                candidates.append(cleaned2)
                
                # 尝试三个序列的组合
                for j in range(i + 1, len(number_sequences)):
                    for k in range(j + 1, len(number_sequences)):
                        combined = number_sequences[i] + number_sequences[j] + number_sequences[k]
                        cleaned = self._clean_card_number(combined)
                        if cleaned:
                            candidates.append(cleaned)
            
            # 传统方法：尝试所有连续组合
            for i in range(len(number_sequences)):
                for j in range(i + 1, min(len(number_sequences) + 1, i + 6)):  # 最多组合6个数字序列
                    combined = ''.join(number_sequences[i:j])
                    cleaned = self._clean_card_number(combined)
                    if cleaned:
                        candidates.append(cleaned)
        
        # 3. 寻找特定长度的单个数字序列
        for seq in number_sequences:
            if 13 <= len(seq) <= 19:
                candidates.append(seq)
        
        return list(set(candidates))  # 去重
    
    def _load_bank_config(self) -> Dict[str, str]:
        """加载银行配置文件"""
        try:
            # 获取当前文件的目录
            current_dir = os.path.dirname(os.path.abspath(__file__))
            config_path = os.path.join(current_dir, 'bank_config.json')
            
            # 如果当前目录没有，尝试上级目录
            if not os.path.exists(config_path):
                parent_dir = os.path.dirname(current_dir)
                config_path = os.path.join(parent_dir, 'src', 'bank_config.json')
            
            if not os.path.exists(config_path):
                # 回退到默认银行名称
                return self._get_default_bank_names()
            
            with open(config_path, 'r', encoding='utf-8') as f:
                bank_config = json.load(f)
            
            # 构建银行名称映射字典
            bank_names = {}
            
            # 中央银行
            for bank in bank_config.get('central_banks', []):
                bank_names[bank] = bank
            
            # 国有银行
            for bank in bank_config.get('state_owned_banks', []):
                bank_names[bank] = bank
                # 添加简称映射，银行卡上通常不显示"中国"
                if '中国' in bank:
                    short_name = bank.replace('中国', '')
                    bank_names[short_name] = bank
                    # 添加更多常见的卡面显示形式
                    if short_name == '工商银行':
                        bank_names['ICBC'] = bank
                        bank_names['工行'] = bank
                    elif short_name == '建设银行':
                        bank_names['CCB'] = bank
                        bank_names['建行'] = bank
                    elif short_name == '农业银行':
                        bank_names['ABC'] = bank
                        bank_names['农行'] = bank
                    elif short_name == '银行':  # 中国银行的特殊情况
                        bank_names['BOC'] = bank
                        bank_names['中行'] = bank
            
            # 政策性银行
            for bank in bank_config.get('policy_banks', []):
                bank_names[bank] = bank
            
            # 股份制银行
            for bank in bank_config.get('joint_stock_banks', []):
                bank_names[bank] = bank
                # 添加简称映射和常见卡面显示
                if bank.endswith('银行'):
                    short_name = bank[:-2]  # 去掉'银行'
                    bank_names[short_name] = bank
                    
                    # 添加特定银行的英文缩写和常见显示
                    if '招商银行' in bank:
                        bank_names['CMB'] = bank
                        bank_names['招行'] = bank
                    elif '交通银行' in bank:
                        bank_names['BOCOM'] = bank
                        bank_names['交行'] = bank
                    elif '中信银行' in bank:
                        bank_names['CITIC'] = bank
                        bank_names['中信'] = bank
                    elif '光大银行' in bank:
                        bank_names['CEB'] = bank
                        bank_names['光大'] = bank
                    elif '民生银行' in bank:
                        bank_names['CMBC'] = bank
                        bank_names['民生'] = bank
                    elif '兴业银行' in bank:
                        bank_names['CIB'] = bank
                        bank_names['兴业'] = bank
                    elif '平安银行' in bank:
                        bank_names['PAB'] = bank
                        bank_names['平安'] = bank
                    elif '浦东发展银行' in bank:
                        bank_names['SPDB'] = bank
                        bank_names['浦发'] = bank
                        bank_names['浦发银行'] = bank
                    elif '华夏银行' in bank:
                        bank_names['HXB'] = bank
                        bank_names['华夏'] = bank
                    elif '广发银行' in bank:
                        bank_names['CGB'] = bank
                        bank_names['广发'] = bank
            
            # 城市商业银行
            for bank in bank_config.get('city_commercial_banks', []):
                bank_names[bank] = bank
                # 添加简称映射
                if bank.endswith('银行'):
                    short_name = bank[:-2]  # 去掉'银行'
                    bank_names[short_name] = bank
                if '股份有限公司' in bank:
                    short_name = bank.replace('股份有限公司', '')
                    bank_names[short_name] = bank
            
            # 农村商业银行
            for bank in bank_config.get('rural_commercial_banks', []):
                bank_names[bank] = bank
                # 添加通用农商银行映射
                if '农村商业银行' in bank:
                    region = bank.replace('农村商业银行', '')
                    if region:
                        bank_names[f'{region}农商银行'] = bank
                        bank_names[f'{region}农商'] = bank
            
            # 农村合作银行
            for bank in bank_config.get('rural_cooperative_banks', []):
                bank_names[bank] = bank
            
            # 农村信用合作社
            for bank in bank_config.get('rural_credit_cooperatives', []):
                bank_names[bank] = bank
                # 添加简称映射
                if '农村信用' in bank:
                    region = bank.split('省')[0] if '省' in bank else bank.split('农村信用')[0]
                    if region:
                        bank_names[f'{region}农信'] = bank
                        bank_names[f'{region}信用社'] = bank
            
            # 村镇银行
            for bank in bank_config.get('village_banks', []):
                bank_names[bank] = bank
            
            # 城市信用合作社
            for bank in bank_config.get('urban_credit_cooperatives', []):
                bank_names[bank] = bank
            
            # 邮政储蓄
            for bank in bank_config.get('postal_savings', []):
                bank_names[bank] = bank
                bank_names['邮储银行'] = bank
                bank_names['邮储'] = bank
            
            # 外资银行
            foreign_banks = bank_config.get('foreign_banks', {})
            for region_banks in foreign_banks.values():
                if isinstance(region_banks, list):
                    for bank in region_banks:
                        bank_names[bank] = bank
                        # 为外资银行添加英文映射
                        if '有限公司' in bank:
                            short_name = bank.replace('有限公司', '')
                            bank_names[short_name] = bank
                        if '（中国）' in bank:
                            short_name = bank.replace('（中国）', '')
                            bank_names[short_name] = bank
            
            # 特殊机构
            for bank in bank_config.get('special_institutions', []):
                bank_names[bank] = bank
            
            # 添加常见的英文银行卡品牌
            bank_names.update({
                'VISA': 'VISA',
                'MASTERCARD': 'MASTERCARD', 
                'UnionPay': 'UnionPay',
                '银联': 'UnionPay',
                'AMERICAN EXPRESS': 'AMERICAN EXPRESS',
                'DISCOVER': 'DISCOVER'
            })
            
            return bank_names
            
        except Exception as e:
            # 加载失败时返回默认配置
            return self._get_default_bank_names()
    
    def _get_default_bank_names(self) -> Dict[str, str]:
        """获取默认银行名称配置"""
        return {
            '工商银行': '中国工商银行',
            '建设银行': '中国建设银行',
            '农业银行': '中国农业银行', 
            '中国银行': '中国银行',
            '招商银行': '招商银行',
            '交通银行': '交通银行',
            '邮储银行': '中国邮政储蓄银行',
            '民生银行': '中国民生银行',
            '光大银行': '中国光大银行',
            '华夏银行': '华夏银行',
            '平安银行': '平安银行',
            '浦发银行': '上海浦东发展银行',
            '兴业银行': '兴业银行',
            '中信银行': '中信银行',
            '农商银行': '农村商业银行',
            '农信': '农村信用合作社',
            'VISA': 'VISA',
            'MASTERCARD': 'MASTERCARD',
            'UnionPay': 'UnionPay',
            '银联': 'UnionPay',
        }
    
    def _extract_bank_name(self, text: str) -> str:
        """从文本中提取银行名称"""
        text_upper = text.upper()
        
        # 排除通用卡片品牌，优先识别具体银行
        excluded_brands = ['UNIONPAY', 'VISA', 'MASTERCARD', 'AMERICAN EXPRESS', 'DISCOVER']
        
        # 按银行名称长度降序排序，优先匹配长名称
        sorted_banks = sorted(self.bank_names.items(), key=lambda x: len(x[0]), reverse=True)
        
        found_banks = []
        
        for bank_key, bank_full_name in sorted_banks:
            # 跳过通用卡片品牌，除非没有找到其他银行
            if bank_key.upper() in excluded_brands:
                continue
                
            # 检查中文名称
            if bank_key in text:
                found_banks.append((bank_full_name, len(bank_key)))
            # 检查英文名称（不区分大小写）
            elif bank_key.upper() in text_upper:
                found_banks.append((bank_full_name, len(bank_key)))
        
        # 如果找到具体银行，返回最长匹配的银行名称
        if found_banks:
            # 按匹配长度降序排序，返回最具体的银行名称
            found_banks.sort(key=lambda x: x[1], reverse=True)
            return found_banks[0][0]
        
        # 如果没有找到具体银行，再考虑通用品牌
        for brand in excluded_brands:
            if brand in text_upper:
                if brand == 'UNIONPAY':
                    return '银联'
                return brand
        
        return ""
    
    def _determine_card_type(self, card_number: str) -> str:
        """根据卡号确定卡片类型"""
        if not card_number or len(card_number) < 4:
            return ""
        
        first_digit = card_number[0]
        first_two = card_number[:2]
        first_four = card_number[:4]
        
        # Visa: 4开头
        if first_digit == '4':
            return "Visa"
        
        # Mastercard: 51-55或2221-2720开头  
        if first_two in ['51', '52', '53', '54', '55']:
            return "Mastercard"
        if '2221' <= first_four <= '2720':
            return "Mastercard"
        
        # 银联: 62开头
        if first_two == '62':
            return "UnionPay"
        
        # American Express: 34或37开头
        if first_two in ['34', '37']:
            return "American Express"
        
        # Discover: 6011, 622126-622925, 644-649, 65开头
        if first_four == '6011' or first_two == '65':
            return "Discover"
        
        return "未知"
    
    def _clean_valid_date(self, date_str: str) -> str:
        """清理和标准化有效期格式"""
        if not date_str:
            return ""
        
        # 移除空格
        date_str = re.sub(r'\s+', '', date_str)
        
        # 处理OCR常见错误字符
        date_str = date_str.replace('!', '1').replace('I', '1').replace('l', '1').replace('O', '0')
        
        def validate_date(mm_str: str, yy_str: str) -> str:
            """验证月份和年份，返回标准格式或空字符串"""
            try:
                mm = int(mm_str)
                yy = int(yy_str)
                
                # 验证月份 1-12
                if not (1 <= mm <= 12):
                    return ""
                
                # 验证年份 >= 25 (即2025年及以后)
                if yy < 25:
                    return ""
                
                return f"{mm:02d}/{yy:02d}"
            except ValueError:
                return ""
        
        # 处理常见的OCR识别错误
        # 如 1110/31 可能是 11/03 的错误识别
        if re.match(r'^\d{4}/\d{2}$', date_str):
            # MMYY/DD 格式，尝试转换为 MM/YY
            mmyy = date_str[:4]
            if len(mmyy) == 4:
                mm = mmyy[:2]
                yy = mmyy[2:]
                result = validate_date(mm, yy)
                if result:
                    return result
        
        # 处理 MMDD/YY 格式，如 1110/31 -> 11/03
        if re.match(r'^\d{2}[01]\d/\d{2}$', date_str):
            mm = date_str[:2]
            remaining = date_str[2:]  # 如 10/31
            if '/' in remaining:
                dd_part, yy_part = remaining.split('/')
                # 针对 1110/31 这种情况，应该解析为 11/03 而不是 10/31
                if len(dd_part) == 2 and len(yy_part) == 2:
                    # 尝试 第三第四位 + 第五位 组成年份
                    # 1110/31 -> 取 1 + 0 = 10，但这不对
                    # 更合理的是: 11 (月份) + 0 (日期的第一位) + 3 (最后两位的第一位) = 03
                    possible_yy = dd_part[1] + yy_part[0]  # 0 + 3 = 03
                    result = validate_date(mm, possible_yy)
                    if result:
                        return result
        
        # 标准格式验证
        if re.match(r'^\d{2}/\d{2}$', date_str):
            mm, yy = date_str.split('/')
            return validate_date(mm, yy)
        
        if re.match(r'^\d{2}/\d{4}$', date_str):
            mm, yyyy = date_str.split('/')
            # 转换为 MM/YY 格式
            yy = yyyy[-2:]
            return validate_date(mm, yy)
        
        # 处理单纯的数字串，如 1103 可能是 11/03
        if re.match(r'^\d{4}$', date_str) and len(date_str) == 4:
            mm = date_str[:2]
            yy = date_str[2:]
            return validate_date(mm, yy)
        
        # 处理可能的1!10格式
        if re.match(r'^\d[!I1l]\d{2}$', date_str):
            # 如 1!10 -> 1110 -> 11/10
            cleaned = date_str.replace('!', '1').replace('I', '1').replace('l', '1')
            if len(cleaned) == 4 and cleaned.isdigit():
                mm = cleaned[:2]
                yy = cleaned[2:]
                return validate_date(mm, yy)
        
        return ""
    
    def parse_bank_card_info(self, text: str) -> BankCardInfo:
        """解析银行卡信息"""
        info = BankCardInfo()
        
        # 预处理文本
        text = re.sub(r'\s+', ' ', text).strip()
        
        # 1. 识别银行卡号（使用新的候选查找方法）
        card_candidates = self._find_card_number_candidates(text)
        
        if card_candidates:
            # 筛选并排序候选卡号：优先级 + Luhn校验 + 长度
            valid_candidates = []
            
            for candidate in card_candidates:
                # 必须通过Luhn校验
                if self._luhn_check(candidate):
                    priority = self._get_card_priority(candidate)
                    if priority > 0:  # 只接受有优先级的卡号
                        valid_candidates.append((candidate, priority, len(candidate)))
            
            if valid_candidates:
                # 按优先级降序，长度降序排序
                valid_candidates.sort(key=lambda x: (x[1], x[2]), reverse=True)
                best_candidate = valid_candidates[0][0]
                
                info.card_number = best_candidate
                info.card_type = self._determine_card_type(best_candidate)
        
        # 2. 识别有效期
        for pattern in self.card_patterns['valid_date']:
            match = re.search(pattern, text, re.IGNORECASE)
            if match:
                raw_date = match.group(1)
                cleaned_date = self._clean_valid_date(raw_date)
                if cleaned_date:
                    info.valid_date = cleaned_date
                    break
        
        # 3. 识别持卡人姓名
        for pattern in self.card_patterns['holder_name']:
            match = re.search(pattern, text, re.IGNORECASE)
            if match:
                name = match.group(1).strip()
                if len(name) >= 2:  # 至少2个字符
                    info.holder_name = name
                    break
        
        # 4. 识别银行名称
        info.bank_name = self._extract_bank_name(text)
        
        return info
    
    def enhance_bank_card_image(self, image_path: str, method: str = 'bilateral') -> str:
        """专门针对银行卡的图像预处理"""
        try:
            import cv2
            import numpy as np
            
            # 读取图像
            img = cv2.imread(image_path)
            if img is None:
                return image_path
                
            # 转换为灰度图
            if len(img.shape) == 3:
                gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            else:
                gray = img
                
            if method == 'bilateral':
                # 双边滤波保持边缘，去除噪声
                processed = cv2.bilateralFilter(gray, 9, 75, 75)
                
                # 自适应阈值，特别适合银行卡的凸字效果
                processed = cv2.adaptiveThreshold(
                    processed, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 4
                )
            else:
                # 备用方法：对比度增强
                clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
                processed = clahe.apply(gray)
                
                # 轻微锐化
                kernel_sharp = np.array([[0,-1,0],
                                       [-1, 5,-1],
                                       [0,-1,0]])
                processed = cv2.filter2D(processed, -1, kernel_sharp)
            
            # 保存处理后的图像
            temp_path = f"temp_enhanced_{method}_{int(time.time() * 1000000)}.jpg"
            cv2.imwrite(temp_path, processed)
            
            return temp_path
            
        except Exception as e:
            return image_path

    def read_bank_card(self, image_path: str) -> BankCardInfo:
        """读取银行卡信息的主函数，包含旋转重试逻辑"""
        temp_files = []
        results = []

        try:
            # 尝试原图
            processed_path = self.resize_image_if_needed(image_path)
            if processed_path != image_path:
                temp_files.append(processed_path)

            ocr_text, boxes = self.ocr_text(processed_path)
            if ocr_text:
                original_result = self.parse_bank_card_info(ocr_text)
                results.append(('original', original_result, ocr_text))

            # 尝试多种图像增强方法
            enhancement_methods = ['bilateral', 'contrast']

            for method in enhancement_methods:
                enhanced_path = self.enhance_bank_card_image(image_path, method)
                if enhanced_path != image_path:
                    temp_files.append(enhanced_path)

                    # 可能需要重新调整大小
                    final_path = self.resize_image_if_needed(enhanced_path)
                    if final_path != enhanced_path:
                        temp_files.append(final_path)
                        enhanced_path = final_path

                    ocr_text_enhanced, _ = self.ocr_text(enhanced_path)
                    if ocr_text_enhanced:
                        enhanced_result = self.parse_bank_card_info(ocr_text_enhanced)
                        results.append((method, enhanced_result, ocr_text_enhanced))

            # 检查是否找到有效的银行卡号
            has_valid_card = any(result.card_number for _, result, _ in results)

            # 如果没有找到有效银行卡号，尝试旋转270度后重新识别
            if not has_valid_card:
                # 旋转270度
                rotated_path = self.rotate_image(image_path, 270)
                if rotated_path != image_path:
                    temp_files.append(rotated_path)

                    # 对旋转后的图片进行识别
                    # 1. 先尝试原始旋转图片
                    processed_rotated = self.resize_image_if_needed(rotated_path)
                    if processed_rotated != rotated_path:
                        temp_files.append(processed_rotated)

                    ocr_text_rotated, _ = self.ocr_text(processed_rotated)
                    if ocr_text_rotated:
                        rotated_result = self.parse_bank_card_info(ocr_text_rotated)
                        results.append(('rotated_270', rotated_result, ocr_text_rotated))

                    # 2. 对旋转后的图片应用增强方法
                    for method in enhancement_methods:
                        enhanced_rotated_path = self.enhance_bank_card_image(rotated_path, method)
                        if enhanced_rotated_path != rotated_path:
                            temp_files.append(enhanced_rotated_path)

                            final_rotated_path = self.resize_image_if_needed(enhanced_rotated_path)
                            if final_rotated_path != enhanced_rotated_path:
                                temp_files.append(final_rotated_path)
                                enhanced_rotated_path = final_rotated_path

                            ocr_text_enhanced_rotated, _ = self.ocr_text(enhanced_rotated_path)
                            if ocr_text_enhanced_rotated:
                                enhanced_rotated_result = self.parse_bank_card_info(ocr_text_enhanced_rotated)
                                results.append((f'rotated_270_{method}', enhanced_rotated_result, ocr_text_enhanced_rotated))

            # 选择最佳结果：优先考虑有卡号且通过Luhn校验的结果
            best_result = None
            best_quality = 0.0

            for method, result, text in results:
                if result.card_number:
                    quality = self._evaluate_result_quality(result)
                    if quality > best_quality:
                        best_quality = quality
                        best_result = result

            if not best_result:
                raise ValueError("未能识别到有效的银行卡信息")

            # 尝试从其他结果补充缺失信息
            for method, result, text in results:
                if result != best_result:
                    # 补充有效期
                    if not best_result.valid_date and result.valid_date:
                        best_result.valid_date = result.valid_date

                    # 补充银行名称
                    if not best_result.bank_name and result.bank_name:
                        best_result.bank_name = result.bank_name

                    # 补充持卡人姓名
                    if not best_result.holder_name and result.holder_name:
                        best_result.holder_name = result.holder_name

            return best_result

        except Exception as e:
            raise Exception(f"银行卡读取失败: {str(e)}")
        finally:
            for temp_file in temp_files:
                try:
                    if os.path.exists(temp_file):
                        os.remove(temp_file)
                except Exception:
                    pass
    
    def _evaluate_result_quality(self, result: BankCardInfo) -> float:
        """评估识别结果质量"""
        score = 0.0
        
        # 银行卡号 (最重要 - 70%)
        if result.card_number:
            if len(result.card_number) in [16, 19] and result.card_number.isdigit():
                score += 0.5
                # 通过Luhn校验加分
                if self._luhn_check(result.card_number):
                    score += 0.2
        
        # 银行名称 (20%)
        if result.bank_name and result.bank_name not in ["未知", ""]:
            score += 0.2
        
        # 有效期 (10%) - 银行卡上通常有此字段
        if result.valid_date:
            score += 0.1
            
        return score

    def rotate_image(self, image_path: str, angle: int = 270) -> str:
        """旋转图片

        Args:
            image_path: 原图片路径
            angle: 旋转角度，默认270度（逆时针）

        Returns:
            旋转后图片的临时路径
        """
        try:
            from PIL import Image

            # 打开图片
            with Image.open(image_path) as img:
                # 旋转图片
                rotated_img = img.rotate(angle, expand=True)

                # 保存到临时文件
                temp_path = f"temp_rotated_{angle}_{int(time.time() * 1000000)}.jpg"
                rotated_img.save(temp_path, 'JPEG', quality=95)

                return temp_path

        except Exception as e:
            # 如果旋转失败，返回原图片路径
            return image_path

if __name__ == "__main__":
    # 简单测试
    reader = BankCardReader()
    print("银行卡识别器初始化完成")
    print(f"使用OCR引擎: {'RapidOCR' if USE_RAPID_OCR else 'PaddleOCR'}")